/*
 * File    : TRTrackerServerPeerImpl.java
 * Created : 5 Oct. 2003
 * By      : Parg 
 * 
 * Azureus - a Java Bittorrent client
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.gudy.azureus2.core3.tracker.server.impl;

import java.net.InetAddress;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.gudy.azureus2.core3.tracker.server.TRTrackerServerPeer;
import org.gudy.azureus2.core3.util.HashWrapper;
import org.gudy.azureus2.core3.util.HostNameToIPResolver;
import org.gudy.azureus2.core3.util.HostNameToIPResolverListener;
import org.gudy.azureus2.core3.util.SystemTime;

import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPosition;

public class TRTrackerServerPeerImpl implements TRTrackerServerPeer, TRTrackerServerSimplePeer, HostNameToIPResolverListener, TRTrackerServerNatCheckerListener {
    private HashWrapper peer_id;
    private int key_hash_code;

    private byte[] ip;
    private boolean ip_override;
    private short tcp_port;
    private short udp_port;
    private short http_port;
    private byte crypto_level;
    private byte az_ver;
    private String ip_str;
    private byte[] ip_bytes;
    private byte NAT_status = NAT_CHECK_UNKNOWN;

    private long timeout;

    private long uploaded;
    private long downloaded;
    private long amount_left;

    private long last_contact_time;
    private boolean download_completed;
    private boolean biased;

    private short up_speed;

    // fields above are serialised when exported

    private DHTNetworkPosition network_position;
    private Object user_data;

    protected TRTrackerServerPeerImpl(HashWrapper _peer_id, int _key_hash_code, byte[] _ip, boolean _ip_override, int _tcp_port, int _udp_port,
            int _http_port, byte _crypto_level, byte _az_ver, long _last_contact_time, boolean _download_completed, byte _last_nat_status,
            int _up_speed, DHTNetworkPosition _network_position) {
        peer_id = _peer_id;
        key_hash_code = _key_hash_code;
        ip = _ip;
        ip_override = _ip_override;
        tcp_port = (short) _tcp_port;
        udp_port = (short) _udp_port;
        http_port = (short) _http_port;
        crypto_level = _crypto_level;
        az_ver = _az_ver;
        last_contact_time = _last_contact_time;
        download_completed = _download_completed;
        NAT_status = _last_nat_status;
        up_speed = _up_speed > Short.MAX_VALUE ? Short.MAX_VALUE : (short) _up_speed;
        network_position = _network_position;

        resolveAndCheckNAT();
    }

    /**
     * Import constructor
     */

    protected TRTrackerServerPeerImpl(HashWrapper _peer_id, int _key_hash_code, byte[] _ip, boolean _ip_override, short _tcp_port, short _udp_port,
            short _http_port, byte _crypto_level, byte _az_ver, String _ip_str, byte[] _ip_bytes, byte _NAT_status, long _timeout, long _uploaded,
            long _downloaded, long _amount_left, long _last_contact_time, boolean _download_completed, boolean _biased, short _up_speed) {
        peer_id = _peer_id;
        key_hash_code = _key_hash_code;
        ip = _ip;
        ip_override = _ip_override;
        tcp_port = _tcp_port;
        udp_port = _udp_port;
        http_port = _http_port;
        crypto_level = _crypto_level;
        az_ver = _az_ver;
        ip_str = _ip_str;
        ip_bytes = _ip_bytes;
        NAT_status = _NAT_status;
        timeout = _timeout;
        uploaded = _uploaded;
        downloaded = _downloaded;
        amount_left = _amount_left;
        last_contact_time = _last_contact_time;
        download_completed = _download_completed;
        biased = _biased;
        up_speed = _up_speed;
    }

    protected boolean update(byte[] _ip, int _port, int _udp_port, int _http_port, byte _crypto_level, byte _az_ver, int _up_speed,
            DHTNetworkPosition _network_position) {
        udp_port = (short) _udp_port;
        http_port = (short) _http_port;
        crypto_level = _crypto_level;
        az_ver = _az_ver;
        up_speed = _up_speed > Short.MAX_VALUE ? Short.MAX_VALUE : (short) _up_speed;
        network_position = _network_position;

        boolean res = false;

        if (_port != getTCPPort()) {

            tcp_port = (short) _port;

            res = true;
        }

        if (!Arrays.equals(_ip, ip)) {

            ip = _ip;

            res = true;
        }

        if (res) {

            resolveAndCheckNAT();
        }

        return (res);
    }

    public void NATCheckComplete(boolean ok) {
        if (ok) {

            NAT_status = NAT_CHECK_OK;

        } else {

            NAT_status = NAT_CHECK_FAILED;
        }
    }

    protected void setNATStatus(byte status) {
        NAT_status = status;
    }

    public byte getNATStatus() {
        return (NAT_status);
    }

    protected boolean isNATStatusBad() {
        return (NAT_status == NAT_CHECK_FAILED || NAT_status == NAT_CHECK_FAILED_AND_REPORTED);
    }

    protected void resolveAndCheckNAT() {
        // default values pending resolution

        ip_str = new String(ip);
        ip_bytes = null;

        HostNameToIPResolver.addResolverRequest(ip_str, this);

        // a port of 0 is taken to mean that the client can't/won't receive incoming
        // connections - tr

        if (tcp_port == 0) {

            NAT_status = NAT_CHECK_FAILED_AND_REPORTED;

        } else {

            // only recheck if we haven't already ascertained the state

            if (NAT_status == NAT_CHECK_UNKNOWN) {

                NAT_status = NAT_CHECK_INITIATED;

                if (!TRTrackerServerNATChecker.getSingleton().addNATCheckRequest(ip_str, getTCPPort(), this)) {

                    NAT_status = NAT_CHECK_DISABLED;
                }
            }
        }
    }

    public void hostNameResolutionComplete(InetAddress address) {
        if (address != null) {

            ip_str = address.getHostAddress();

            ip_bytes = address.getAddress();
        }
    }

    protected long getLastContactTime() {
        return (last_contact_time);
    }

    protected boolean getDownloadCompleted() {
        return (download_completed);
    }

    protected void setDownloadCompleted() {
        download_completed = true;
    }

    public boolean isBiased() {
        return (biased);
    }

    public void setBiased(boolean _biased) {
        biased = _biased;
    }

    public HashWrapper getPeerId() {
        return (peer_id);
    }

    public byte[] getPeerID() {
        return (peer_id.getBytes());
    }

    protected int getKeyHashCode() {
        return (key_hash_code);
    }

    public byte[] getIPAsRead() {
        return (ip);
    }

    public String getIPRaw() {
        return (new String(ip));
    }

    /**
     * If asynchronous resolution of the address is required, this will return the non-resolved address until the async process completes
     */

    public String getIP() {
        return (ip_str);
    }

    protected boolean isIPOverride() {
        return (ip_override);
    }

    /**
     * This will return in resolution of the address is not complete or fails
     * 
     * @return
     */

    public byte[] getIPAddressBytes() {
        return (ip_bytes);
    }

    public int getTCPPort() {
        return (tcp_port & 0xffff);
    }

    public int getUDPPort() {
        return (udp_port & 0xffff);
    }

    public int getHTTPPort() {
        return (http_port & 0xffff);
    }

    public byte getCryptoLevel() {
        return (crypto_level);
    }

    public byte getAZVer() {
        return (az_ver);
    }

    public int getUpSpeed() {
        return (up_speed & 0xffff);
    }

    public DHTNetworkPosition getNetworkPosition() {
        return (network_position);
    }

    protected void setTimeout(long _now, long _timeout) {
        last_contact_time = _now;

        timeout = _timeout;
    }

    protected long getTimeout() {
        return (timeout);
    }

    public int getSecsToLive() {
        return ((int) ((timeout - SystemTime.getCurrentTime()) / 1000));
    }

    protected void setStats(long _uploaded, long _downloaded, long _amount_left) {
        uploaded = _uploaded;
        downloaded = _downloaded;
        amount_left = _amount_left;
    }

    public long getUploaded() {
        return (uploaded);
    }

    public long getDownloaded() {
        return (downloaded);
    }

    public long getAmountLeft() {
        return (amount_left);
    }

    public boolean isSeed() {
        return (amount_left == 0);
    }

    public void setUserData(Object key, Object data) {
        if (user_data == null) {

            user_data = new Object[] { key, data };

        } else if (user_data instanceof Object[]) {

            Object[] x = (Object[]) user_data;

            if (x[0] == key) {

                x[1] = data;

            } else {

                HashMap map = new HashMap();

                user_data = map;

                map.put(x[0], x[1]);

                map.put(key, data);
            }
        } else {

            ((Map) user_data).put(key, data);
        }
    }

    public Object getUserData(Object key) {
        if (user_data == null) {

            return (null);

        } else if (user_data instanceof Object[]) {

            Object[] x = (Object[]) user_data;

            if (x[0] == key) {

                return (x[1]);

            } else {

                return (null);
            }
        } else {

            return (((Map) user_data).get(key));
        }
    }

    public Map export() {
        Map map = new HashMap();

        map.put("peer_id", peer_id.getBytes());
        map.put("key_hash_code", new Long(key_hash_code));
        map.put("ip", ip);
        map.put("ip_override", new Long(ip_override ? 1 : 0));
        map.put("tcp_port", new Long(tcp_port));
        map.put("udp_port", new Long(udp_port));
        map.put("http_port", new Long(http_port));
        map.put("crypto_level", new Long(crypto_level));
        map.put("az_ver", new Long(az_ver));
        map.put("ip_str", ip_str);
        if (ip_bytes != null) {
            map.put("ip_bytes", ip_bytes);
        }
        map.put("NAT_status", new Long(NAT_status));
        map.put("timeout", new Long(timeout));
        map.put("uploaded", new Long(uploaded));
        map.put("downloaded", new Long(downloaded));
        map.put("amount_left", new Long(amount_left));
        map.put("last_contact_time", new Long(last_contact_time));
        map.put("download_completed", new Long(download_completed ? 1 : 0));
        map.put("biased", new Long(biased ? 1 : 0));
        map.put("up_speed", new Long(up_speed));

        return (map);
    }

    public static TRTrackerServerPeerImpl importPeer(Map map) {
        try {
            HashWrapper peer_id = new HashWrapper((byte[]) map.get("peer_id"));
            int key_hash_code = ((Long) map.get("key_hash_code")).intValue();
            byte[] ip = (byte[]) map.get("ip");
            boolean ip_override = ((Long) map.get("ip_override")).intValue() == 1;
            short tcp_port = ((Long) map.get("tcp_port")).shortValue();
            short udp_port = ((Long) map.get("udp_port")).shortValue();
            short http_port = ((Long) map.get("http_port")).shortValue();
            byte crypto_level = ((Long) map.get("crypto_level")).byteValue();
            byte az_ver = ((Long) map.get("az_ver")).byteValue();
            String ip_str = new String((byte[]) map.get("ip_str"));
            byte[] ip_bytes = (byte[]) map.get("ip_bytes");
            byte NAT_status = ((Long) map.get("NAT_status")).byteValue();
            long timeout = ((Long) map.get("timeout")).longValue();
            long uploaded = ((Long) map.get("uploaded")).longValue();
            long downloaded = ((Long) map.get("downloaded")).longValue();
            long amount_left = ((Long) map.get("amount_left")).longValue();
            long last_contact_time = ((Long) map.get("last_contact_time")).longValue();
            boolean download_completed = ((Long) map.get("download_completed")).intValue() == 1;
            boolean biased = ((Long) map.get("biased")).intValue() == 1;
            short up_speed = ((Long) map.get("up_speed")).shortValue();

            return (new TRTrackerServerPeerImpl(peer_id, key_hash_code, ip, ip_override, tcp_port, udp_port, http_port, crypto_level, az_ver,
                    ip_str, ip_bytes, NAT_status, timeout, uploaded, downloaded, amount_left, last_contact_time, download_completed, biased,
                    up_speed));

        } catch (Throwable e) {

            return (null);
        }
    }

    protected String getString() {
        return (new String(ip) + ":" + getTCPPort() + "(" + new String(peer_id.getHash()) + ")");
    }
}
